#pragma once

#include "Engine/Hdr.h"

const int TILE_SIZE = 40;
const int SCREEN_WIDTH = 1280;
const int SCREEN_HEIGHT = 720;
const int LEVEL_WIDTH = SCREEN_WIDTH/TILE_SIZE;
const int LEVEL_HEIGHT = SCREEN_HEIGHT/TILE_SIZE;
extern const Vec HALF_TILE_SIZE;

extern const double BEAT;
static const int TAKT = 4;
extern double BeatPercent;
extern double TaktPercent;

inline Vec CoordToCenter(Vec tile_coords)
{
	return tile_coords * TILE_SIZE + HALF_TILE_SIZE;
}

inline Vec PosToCoord(Vec pixel_pos)
{
	return (pixel_pos/TILE_SIZE).Floored();
}

struct Tile;

struct CreepCommand
{
	int CreepType;
	size_t Count;
};

struct CreepWave
{
	eastl::vector<CreepCommand> Commands;
};

struct Level
{
	Level() {}

	Grid<Tile*> Tiles;
	eastl::vector<Vec> Path;
	eastl::vector<CreepWave> Waves;

	Vec SpawnPos;

	void LoadFromFile(const char* filename);
};

enum {
	WAVE_SPAWNING,
	WAVE_WAITING_FOR_DEAD,
	WAVE_BREATH_BETWEEN_WAVES,
};

struct CDObj;

struct Game : public World
{
	Game() : World() {}

	void Update(float dt);
	void Render();
	void RenderGUI();
	void InitGraphics();
	void InitGameplay();

	void AnimEvent(const char* event, const Prop& arg);
	void GameEvent(const char* event, const Prop& arg);

	void LoadLevel(const char* filename);

	Level CurrentLevel;
	int CurrentWave;
	int CurrentWaveCommand;
	double LastWaveCommandTime;
	bool LevelComplete;

	int WaveState;
	double NextWaveTime;

	DisplayVar<size_t> Money;

  Fnt FontSmall;
  Fnt FontMedium;
  Fnt FontLarge;

	Snd SndShot;
	Snd SndClick;
	Snd SndNo;
	Snd SndBlip;

	struct Menu
	{
		struct MenuButton
		{
			eastl::string Description;
			eastl::string Image;
			Prop Data;
			bool Enabled;
			void (*ClickFunc)(Game*, MenuButton*);
			int Draw(int uid, Vec at, int flags);

			MenuButton() : ClickFunc(0), Enabled(true) {}
		};

		eastl::vector<MenuButton> Buttons;
	};

	bool ShowHUD;
	float HUDAnim, DestHUDAnim;
	float StartHUDAngle;
	Menu CurrentMenu;
	Vec MenuPos;
	int LastGUIItem;

	CDObj* SelectedObj;

	void ShowMenu(Vec at);

	void SpawnCreep();
	void SpawnCreep(int type);
	void BuildTower(Vec at, int type);
};

struct CDObj : public Obj
{
	CDObj() : Obj(), Selected(false), Lighten(0) {}

	bool Selected;
  float Lighten;

	Level& GetLevel() { return ((Game*)Wrld)->CurrentLevel; }
	Game& GetGame() { return *((Game*)Wrld); }

	virtual void InitGraphics() {}
	virtual void InitGameplay() {}

	virtual void Update(float dt) { Obj::Update(dt); }
	virtual void Event(int event, const Prop& arg);

	virtual void AnimEvent(const char* event, const Prop& arg) {}

	template <typename T>
	Obj* FindClosest(int of_type, float range, T condition)
	{
		Obj* closest = 0;
		float closest_dist = 100000000.0f;
		range = range*range;
		for (size_t i=0; i<GetGame().Objects.size(); i++)
		{
			Obj* obj = GetGame().Objects[i];
			if (obj != this && !obj->Dead && obj->Type() == of_type && (!condition || condition(obj)))
			{
				float dist = obj->Pos.DistanceSquared(this->Pos);
				if (dist < range && dist < closest_dist)
				{
					closest = obj;
					closest_dist = dist;
				}
			}
		}
		return closest;
	}

	void Select()
	{
		if (this != GetGame().SelectedObj)
		{
			PlaySnd(GetGame().SndBlip, 1.0f, false);
			if (GetGame().SelectedObj)
				GetGame().SelectedObj->Selected = false;
			GetGame().SelectedObj = this;
			Selected = true;
		}
	}
};

enum {
	TILE_VOID,
	TILE_GRASS,
	TILE_ROAD,
	TILE_BASE,
	TILE_SPAWN,
};

struct Tile : public CDObj
{
	int TileType;
	size_t X, Y;

	Tile() : TileType(TILE_VOID) { Size = Vec(TILE_SIZE, TILE_SIZE); }

	virtual int Type() { return 'Tile'; }

	virtual void Render(const Vec& offset);
	virtual void Event(int event, const Prop& arg);

protected:
  virtual void DrawRoad(const Vec& offset);
  virtual void DrawSpawn(const Vec& offset);
  virtual void DrawBase(const Vec& offset);
};

enum {
	CREEP_NORMAL,
	CREEP_FLYER,
	CREEP_TANK,

	ALL_CREEPS
};

enum {
	EFFECT_SLOWED,

	ALL_EFFECTS,
};

struct Tower;

struct CCreepData
{
	int ID;
	const char* Name;
	float Speed;
	float Life;
	int Money;

	operator eastl::map<eastl::string, int>::value_type () const
	{
		return eastl::map<eastl::string, int>::value_type(Name, ID);
	}
};

extern CCreepData CreepData[ALL_CREEPS];

struct Creep : public CDObj
{
	int NextPathPoint;
	int CreepType;

	float Speed;
	float Health;
  float RealRot;

	//double StatusEffects[ALL_EFFECTS];

  Creep() : NextPathPoint(0), CreepType(CREEP_NORMAL), RealRot(0) { }

	void InitType(int type);

	virtual int Type() { return 'Crep'; }

	virtual void Update(float dt);
	virtual void Render(const Vec& offset);
	virtual void Event(int event, const Prop& arg);
  virtual void AnimEvent(const char* event, const Prop& arg);

	void Damage(float damage);
	void TowerHit(Tower* tower);

protected:
  virtual void DrawNormal(const Vec& offset);
  virtual void DrawFlyer(const Vec& offset);
  virtual void DrawTank(const Vec& offset);
};

enum {
	TOWER_BEAT_RIFLE,
	TOWER_CHIPSTEP_COIL,
	TOWER_SNARE_GUN,
	TOWER_BASE_CANNON,

	ALL_TOWERS,
};

struct Tower : public CDObj
{
	int TowerType;
	double LastShot;

	void InitType(int type);

	float Range;
	float Power;
	float Damage;
	float SpeedMultiplier;

	void Fire();

	Tower() : TowerType(TOWER_BEAT_RIFLE), LastShot(0) { 
		Circular = true;
		Radius = TILE_SIZE/2;
	}

	static void DrawRange(Vec pos, float range);
	static void DrawPreview(int type, Vec at);

	virtual int Type() { return 'Towr'; }

	virtual void Update(float dt);
	virtual void Render(const Vec& offset);
	virtual void Event(int event, const Prop& arg);
  virtual void AnimEvent(const char* event, const Prop& arg);

private:
  static float mLighten;
  static void DrawBeatRifle(const Vec& at);
  static void DrawChipstepCoil(const Vec& at);
  static void DrawSnareGun(const Vec& at);
  static void DrawBaseCannon(const Vec& at);
};